﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace HP.Cachalote.WPF.ExtendedAttachedProperties
{
    /// <summary>
    /// Try use current ViewModel to get the related TextBox instance. 
    /// Then you can do anything you want on the TextBox.
    /// This methodology can separate View and ViewModel to help you maintain a pure MVVM structure.
    /// </summary>
    public class TextBoxExtendedAttachedProperties
    {
        public static readonly DependencyProperty TextBoxControllerProperty = DependencyProperty.RegisterAttached("TextBoxController", typeof(ITextBoxController),
            typeof(TextBoxExtendedAttachedProperties), new FrameworkPropertyMetadata(null, OnTextBoxControllerChanged));

        public static void SetTextBoxController(UIElement element, ITextBoxController value)
        {
            element.SetValue(TextBoxControllerProperty, value);
        }

        public static ITextBoxController GetTextBoxController(UIElement element)
        {
            return (ITextBoxController)element.GetValue(TextBoxControllerProperty);
        }

        private static readonly Dictionary<ITextBoxController, TextBox> elements = new Dictionary<ITextBoxController, TextBox>();

        /// <summary>
        /// This callback will be called by each TextBox which using TextBoxExtendedAttachedProperties.TextBoxController="{Binding}".
        /// Then we just can use a key/value paired collection to store all TextBoxes on current view.
        /// The key of the colloection is each ViewModel.
        /// </summary>
        /// <param name="d">The DependencyObject on which the property has changed value.</param>
        /// <param name="e">Event data that is issued by any event that tracks changes to the effective value of this property.</param>
        private static void OnTextBoxControllerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var element = d as TextBox;

            if (element == null)
            {
                throw new ArgumentNullException("d");
            }

            //Try to remove the unused TextBox from current collection.
            var oldController = e.OldValue as ITextBoxController;

            if (oldController != null)
            {
                elements.Remove(oldController);
                oldController.SetFocus -= SetFocus;
                oldController.MoveFocusToNext -= MoveFocusToNext;
                oldController.MoveCursorToBeginning -= MoveCursorToBeginning;
            }

            //Try to add new TextBox into current collection.
            var newController = e.NewValue as ITextBoxController;

            if (newController != null && !elements.ContainsKey(newController))
            {
                elements.Add(newController, element);
                newController.SetFocus += SetFocus;
                newController.MoveFocusToNext += MoveFocusToNext;
                newController.MoveCursorToBeginning += MoveCursorToBeginning;

                if (newController.SetFocusInstantly)
                {
                    element.Focus();
                }
            }
        }

        /// <summary>
        /// Try to set sepcified TextBox to be focused.
        /// </summary>
        /// <param name="sender">This is supposed to be current ViewModel.</param>
        private static void SetFocus(ITextBoxController sender)
        {
            TextBox element;

            if (!elements.TryGetValue(sender, out element))
            {
                throw new ArgumentException("sender");
            }

            element.Focus();
        }

        /// <summary>
        /// Try to move focus from sepcified TextBox to next element.
        /// </summary>
        /// <param name="sender">This is supposed to be current ViewModel.</param>
        private static void MoveFocusToNext(ITextBoxController sender)
        {
            TextBox element;

            if (!elements.TryGetValue(sender, out element))
            {
                throw new ArgumentException("sender");
            }

            element.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
        }

        /// <summary>
        /// Try to move cursor to the beginning of the TextBox.
        /// </summary>
        /// <param name="sender">This is supposed to be current ViewModel.</param>
        private static void MoveCursorToBeginning(ITextBoxController sender)
        {
            TextBox element;

            if (!elements.TryGetValue(sender, out element))
            {
                throw new ArgumentException("sender");
            }

            element.Select(0, 0);;
        }
    }

    public interface ITextBoxController
    {
        bool SetFocusInstantly { get; set; }
        event TextBoxControllerEventHandler SetFocus;
        event TextBoxControllerEventHandler MoveFocusToNext;
        event TextBoxControllerEventHandler MoveCursorToBeginning;
    }

    public delegate void TextBoxControllerEventHandler(ITextBoxController sender);
}
